#!/usr/bin/env bash
# Clang source-based C++ coverage for the latte-dock test suite. Configures a
# dedicated clang coverage build, runs ctest with per-process profraw, merges,
# exports per-file line coverage, and writes build/_coverage/cxx-cov.json.
# Needs clang + llvm-tools and the Qt6/KF6 build deps.
#
# Runtime note: the GLOB-behavioral tests that construct a real Latte::Corona
# (layoutsmodeltest, viewsmodeltest, ...) need the Latte KPackages installed to a
# location on XDG_DATA_DIRS, or Corona's shell package reads invalid and those tests
# abort in initTestCase, dropping their coverage. Install them into the build
# environment once (e.g. `sudo cmake --install <build> --prefix /usr`) before measuring.
set -euo pipefail

REPO="$(cd "$(dirname "$0")/../.." && pwd)"
COV_DIR="${COV_DIR:-$REPO/build-coverage}"
OUT="${OUT:-$REPO/build/_coverage}"
mkdir -p "$OUT"

configure() {
    cmake -B "$COV_DIR" -S "$REPO" -G Ninja \
        -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang \
        -DLATTE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON
}

echo "== configure (clang, LATTE_COVERAGE=ON) =="
configure

# The GLOB-behavioral tests freeze latte-dock's *.cpp.o into their link lists at
# configure time (file(GLOB_RECURSE ...) over the .dir), so every app object must
# already exist on disk before those lists are captured. Build the app first, then
# reconfigure so the globs pick up all objects (including newly-added sources), then
# build the tests. Without this, a fresh build or a newly-added source links the GLOB
# tests against a stale object set and fails with undefined references.
echo "== build app objects, reconfigure, build everything =="
cmake --build "$COV_DIR" --target latte-dock -j"$(nproc)"
configure
cmake --build "$COV_DIR" -j"$(nproc)"

echo "== run instrumented tests =="
# Absolute LLVM_PROFILE_FILE: configuring -S . puts the tests in build-coverage/tests/,
# so a relative path would scatter profraw under each test's working dir and the
# merge glob below would miss them. Anchor every process to one dir.
# Exclude the QML shell-script gates: they produce no C++ coverage and are measured
# by qml_coverage.sh separately.
rm -rf "$COV_DIR/coverage"; mkdir -p "$COV_DIR/coverage"
# A failing test still executed code before it failed and wrote a valid profraw, so a
# test failure must not abort the measurement: tests needing a live compositor or
# external Plasma packages fail headlessly yet still contribute their executed lines.
( cd "$COV_DIR" && QT_QPA_PLATFORM=offscreen \
    LLVM_PROFILE_FILE="$COV_DIR/coverage/%p.profraw" \
    ctest -E 'qmlloadcompile|qmlinteraction' --output-on-failure ) \
    || echo "  (some tests failed; coverage is still measured from their profraw)"

echo "== merge =="
llvm-profdata merge -sparse "$COV_DIR"/coverage/*.profraw \
    -o "$COV_DIR/coverage/merged.profdata"

echo "== export =="
# Object set = exactly the instrumented targets (CMake writes cov_targets.txt from
# _latte_cov_targets), so uninstrumented manual-test binaries can't slip in.
if [ ! -f "$COV_DIR/cov_targets.txt" ]; then
    echo "cov_targets.txt missing — reconfigure with -DLATTE_COVERAGE=ON"; exit 2
fi
bins=()
while IFS= read -r _n; do
    [ -n "$_n" ] && [ -x "$COV_DIR/bin/$_n" ] && bins+=("$COV_DIR/bin/$_n")
done < "$COV_DIR/cov_targets.txt"
if [ "${#bins[@]}" -eq 0 ]; then echo "no instrumented test binaries found"; exit 2; fi
main="${bins[0]}"; rest=(); for b in "${bins[@]:1}"; do rest+=("-object=$b"); done
# Drop *test.cpp driver files (incl. covselftest.cpp) but KEEP production sources
# and the covself.cpp fixture, so verify_harness.py can see the self-test file.
llvm-cov export -instr-profile="$COV_DIR/coverage/merged.profdata" -format=text \
    "$main" "${rest[@]}" \
    -ignore-filename-regex='(^/usr/|/Qt6/|/KF6|/tests/.*test\.cpp$|_autogen/|moc_|/build-coverage/)' \
    > "$OUT/cxx-export.json"

echo "== report =="
python3 "$REPO/tests/coverage/cxx_report.py" \
    --export "$OUT/cxx-export.json" --json-out "$OUT/cxx-cov.json" --repo-root "$REPO"
